﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FarseerGames.FarseerPhysics;
using FarseerGames.FarseerPhysics.Dynamics;
using FarseerGames.FarseerPhysics.Collisions;
using FarseerGames.FarseerPhysics.Factories;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler;
using System.Diagnostics;

namespace FolderData
{
    public class Terrain : Substance
    {
        #region Definitions
        public enum TerrainKind
        {
            GROUND = 0,
            GATE = 1,
            BRAMBLES = 2,
            CHECK_POINT = 3,
            /// <summary>
            /// 몬스터를 가로막는 가상의 벽.
            /// </summary>
            MONSTER_BLOCK = 5
        }

        public class TerrainDataSet : Substance.DataSet
        {
            protected TerrainKind _kind;
            public TerrainKind Kind
            {
                get { return _kind; }
                set { _kind = value; }
            }
            public override void Serialize(ContentWriter output)
            {
                base.Serialize(output);
                output.Write((int)Kind);
            }
            public override void Deserialize(ContentReader input)
            {
                base.Deserialize(input);
                this.Kind = (TerrainKind)input.ReadInt32();
            }
        }
        #endregion

        #region Fields
        protected TerrainKind _kind;
        protected string[] _spriteAssetNames;       //스프라이트 asset의 이름.
        protected Texture2D[] _sprites;
        protected int _spriteIndicator;             //현재 보여줄 스프라이트 번호.
        protected int _elapsedTime;                 //진행된 시간. 체크포인트 변환과 같은 애니메이션에서 사용. ms
        protected readonly int TerrainAnimationDuration = 100;//Validate<->InValidate간의 애니메이션 duration. ms
        protected readonly int GateAnimationDuration = 400; //Validate <-> InValidate간의 총 애니메이션 duration. ms 
        protected List<NextStateCallBack> _nextCallbacks;

        [ReadOnly(true)]
        public TerrainKind Kind
        {
            get { return _kind; }
            set 
            { 
                _kind = value;
                //Checkpoint관련된 kind이면 state를 초기화 해준다.
                if (_kind == TerrainKind.CHECK_POINT)
                    _state = SubstanceState.INVALIDATED;      //Check Point는 기본적으로 Invalid 상태이다.
                else if (_kind == TerrainKind.GATE)
                    _state = SubstanceState.VALIDATED;        //Gate는 기본적으로 Valid 상태이다.
            }
        }
        [Browsable(false)]
        public string[] SpriteAssetNames
        {
            get { return _spriteAssetNames; }
            set { _spriteAssetNames = value; }
        }

        public override Texture2D Texture
        {
            get 
            {
                return _sprites == null ? null : _sprites[_spriteIndicator];
            }
        }
        public override Rectangle? Rect
        {
            get { return null; }
        }
        /// <summary>
        /// 현재 Terrain의 Validation을 변경합니다. true면 Valid로, false면 Invalid로 만듭니다.
        /// </summary>
        public override bool Activation
        {
            get 
            {
                return base.Activation;
            }
            set 
            {
                if (value)
                    _nextCallbacks[(int)this.Kind](SubstanceState.VALIDATED);
                else
                    _nextCallbacks[(int)this.Kind](SubstanceState.INVALIDATED);
            }
        }
        #endregion

        public override void BindSimulator(PhysicsSimulator simulator)
        {
            base.BindSimulator(simulator);
            makeBodyGeom(Mass, Verts, out _body, out _geom, out _origin);
            simulator.Add(_body);
            simulator.Add(_geom);
            _body.IsStatic = true;
        }

        public override void UnbindSimulator()
        {
            base.UnbindSimulator();
            _simulator.Remove(_body);
            _simulator.Remove(_geom);
        }

        public override object Clone()
        {
            Terrain tr = new Terrain(_state);
            Substance._clone(this as Substance, tr as Substance);
            tr.SpriteAssetNames = this.SpriteAssetNames;
            tr.Kind = this.Kind;
            tr.SetTextures(_sprites);
            return tr as object;
            //주의!
            //Clone시 _elapsedTime은 제대로 복사되지 않을 것임.
        }

        public override void Update(GameTime gameTime)
        {
            if (Kind == TerrainKind.CHECK_POINT)
            {
                //회전!
                this.Rotation += 0.01f;

                if (_state == SubstanceState.VALIDATING || _state == SubstanceState.INVALIDATING)
                {
                    _elapsedTime += gameTime.ElapsedGameTime.Milliseconds;
                    if (_elapsedTime >= TerrainAnimationDuration)
                        _nextCallbacks[(int)this.Kind](_state == SubstanceState.VALIDATING ? SubstanceState.VALIDATED : SubstanceState.INVALIDATED);
                }
            }
            else if (Kind == TerrainKind.GATE)
            {
                if (_state == SubstanceState.VALIDATING || _state == SubstanceState.INVALIDATING)
                {
                    _elapsedTime += gameTime.ElapsedGameTime.Milliseconds;
                    _nextCallbacks[(int)this.Kind](_state == SubstanceState.VALIDATING ? SubstanceState.VALIDATED : SubstanceState.INVALIDATED);
                }
            }
        }

        public void SetDatas(TerrainDataSet ds)
        {
            base.SetDatas(ds as Substance.DataSet);
            this.Kind = ds.Kind;
            if (ds.IsVirtualGeom)
            {
                _simulator.Remove(_body);
                _simulator.Remove(_geom);
            }
        }

        public TerrainDataSet GetDatas()
        {
            TerrainDataSet ds = new TerrainDataSet();
            _getDatas(ds);
            ds.Kind = this.Kind;
            return ds;
        }

        public void SetTextures(Texture2D[] sprites)
        {
            _sprites = sprites;
        }

        #region nextCallbacks
        /// <summary>
        /// 원하는 state쪽으로 현재 애니메이션과 스테이트를 변경한다.
        /// _state, _elapsedTime을 변경하니 조심할 것.
        /// </summary>
        protected void nextCheckpointState(SubstanceState state)
        {
            Debug.Assert(Kind == TerrainKind.CHECK_POINT);
            if (state == SubstanceState.INVALIDATED)
            {
                _state = SubstanceState.INVALIDATING;
                --_spriteIndicator;
                if (_spriteIndicator <= 0)
                {
                    _spriteIndicator = 0;
                    _state = SubstanceState.INVALIDATED;
                }
                _elapsedTime = 0;

            }
            else if (state == SubstanceState.VALIDATED)
            {
                _state = SubstanceState.VALIDATING;
                ++_spriteIndicator;
                if (_spriteIndicator >= _sprites.Length)
                {
                    _spriteIndicator = _sprites.Length - 1;
                    _state = SubstanceState.VALIDATED;
                }
                _elapsedTime = 0;
            }
            else
                throw new ArgumentException("State가 Invalidated 또는 Validated가 아닙니다.");
        }

        private void nextGateState(SubstanceState state)
        {
            Debug.Assert(Kind == TerrainKind.GATE);
            if (state == SubstanceState.INVALIDATED)
            {
                if (_state != SubstanceState.INVALIDATING)
                {
                    _state = SubstanceState.INVALIDATING;
                    _elapsedTime = 0;
                }
                _colorMod = new Color(1f, 1f, 1f, (GateAnimationDuration - (float)_elapsedTime) / GateAnimationDuration);
                if (_elapsedTime >= GateAnimationDuration)
                {
                    _state = SubstanceState.INVALIDATED;
                    _elapsedTime = 0;
                    _colorMod = new Color(0f, 0f, 0f, 0f);
                    _geom.CollisionEnabled = false;
                }
            }
            else if (state == SubstanceState.VALIDATED)
            {
                if (_state != SubstanceState.VALIDATING)
                {
                    _state = SubstanceState.VALIDATING;
                    _elapsedTime = 0;
                }
                _colorMod = new Color(1, 1, 1, ((float)_elapsedTime) / GateAnimationDuration);
                if (_elapsedTime >= GateAnimationDuration)
                {
                    _state = SubstanceState.VALIDATED;
                    _elapsedTime = 0;
                    _colorMod = Color.White;
                    _geom.CollisionEnabled = true;
                }
            }
            else
                throw new ArgumentException("State가 Invalidated 또는 Validated가 아닙니다.");
        }

        private void nextNotDefined(SubstanceState state)
        {
            throw new NotImplementedException();
        }
        #endregion

        #region Constructors
        public Terrain()
            : base()
        {
            _spriteIndicator = 0;
            _elapsedTime = 0;
            _nextCallbacks = new List<NextStateCallBack>();   
            _nextCallbacks.Add(new NextStateCallBack(nextNotDefined));
            _nextCallbacks.Add(new NextStateCallBack(nextGateState));
            _nextCallbacks.Add(new NextStateCallBack(nextNotDefined));
            _nextCallbacks.Add(new NextStateCallBack(nextCheckpointState));
        }

        protected Terrain(SubstanceState state) //for cloning
            : this()
        {
            this._state = state;
        }
        #endregion

        #region Processor
        public class TerrainReader : ContentTypeReader<Terrain>
        {
            protected override Terrain Read(ContentReader input, Terrain existingInstance)
            {
                Terrain t = new Terrain();
                t.Name = input.ReadString();
                t.Verts = new Vertices();
                int count = input.ReadInt32();
                for (int i = 0; i < count; ++i)
                    t.Verts.Add(new Vector2(input.ReadSingle(), input.ReadSingle()));
                t.TextureVerts = new Vertices();
                count = input.ReadInt32();
                for (int i = 0; i < count; ++i)
                    t.TextureVerts.Add(new Vector2(input.ReadSingle(), input.ReadSingle()));
                t.Mass = input.ReadSingle();
                t.Layer = (LayerOrder)input.ReadInt32();
                t._property = (Properties)input.ReadUInt64();
                t.Kind = (TerrainKind)input.ReadInt32();
                t.SpriteAssetNames = new string[input.ReadInt32()];
                Texture2D[] ts = new Texture2D[t.SpriteAssetNames.Length];
                for (int i = 0; i < t.SpriteAssetNames.Length; ++i)
                {
                    t.SpriteAssetNames[i] = input.ReadString();
                    ts[i] = input.ContentManager.Load<Texture2D>(t.SpriteAssetNames[i]);
                }
                t.SetTextures(ts);
                return t;
            }
        }

        [ContentTypeWriter]
        public class TerrainWriter : ContentTypeWriter<Terrain>
        {
            protected override void Write(ContentWriter output, Terrain value)
            {
                output.Write(value.Name);
                output.Write(value.Verts.Count);
                for (int i = 0; i < value.Verts.Count; ++i)
                {
                    output.Write(value.Verts[i].X);
                    output.Write(value.Verts[i].Y);
                }
                output.Write(value.TextureVerts.Count);
                for (int i = 0; i < value.TextureVerts.Count; ++i)
                {
                    output.Write(value.TextureVerts[i].X);
                    output.Write(value.TextureVerts[i].Y);
                }
                output.Write(value.Mass);
                output.Write((int)value.Layer);
                output.Write((UInt64)value._property);
                output.Write((int)value.Kind);
                output.Write(value.SpriteAssetNames.Length);
                for (int i = 0; i < value.SpriteAssetNames.Length; ++i)
                    output.Write(value.SpriteAssetNames[i]);
            }
            public override string GetRuntimeReader(TargetPlatform targetPlatform)
            {
                return typeof(Terrain.TerrainReader).AssemblyQualifiedName;
            }
            public override string GetRuntimeType(TargetPlatform targetPlatform)
            {
                return typeof(Terrain).AssemblyQualifiedName;
            }
        }
        #endregion
    }
}